认证和认证状态的保持 - Session 和 JWT Token

认证的本质

用一句话总结,认证(Authentication)的本质,就是物理世界里的人在数字世界中确立主体 (Subject)身份的过程——通过在无信任网络中交换凭证、建立信任,从而完成身份的核验与延续。

在现实中,我们确认一个人的身份靠的是长相、声音和相处记忆。但在数字世界里,服务器与你隔着万水千山和冷冰冰的协议。服务器永远无法直接 “看到” 屏幕前的那个血肉之躯,它只能感知你发送的数据。因此,登录认证的第一重本质是映射(Mapping)

1
你的肉身(物理实体) -> 产生特定证据 -> 映射为数据库中的一行记录(User ID: 1024)。

这非常像哲学上的 “表象与物自体”——系统无法触碰你的本质(物自体),它只能通过你提供的 “证据(表象)” 来推断 “你就是你”。


身份证据的三个维度

为了完成这个映射,系统必须向你索要证据。在计算机安全学中,这些证据被经典地划分为三个维度:

  • 你所知道的(Something you know): 密码、密保问题、手势轨迹。本质是 “记忆的特权”。但缺点很明显,那就是容易被遗忘,也容易被偷看或暴力破解。
  • 你所拥有的(Something you have): 手机(收验证码)、U盾、硬件 Key、绑定了特定 Cookie 的浏览器。本质是 “物理控制权的转移”。你拥有这个设备,就代表你拥有这个身份。
  • 你所特有的(Something you are): 指纹、人脸、虹膜、甚至击键律。本质是 “生物特征的唯一性”。无法更改,一旦泄露就是终身泄露。

现代安全的本质,就是这三种证据的交叉组合(MFA, 多因素认证)以及持续验证(零信任)。


认证状态的延续

如果仅仅是证明“我是我”,那只需要在每次请求时都输入一次密码。但这在体验上是灾难性的。因为网络协议(如 HTTP)是无状态(Stateless)的,每一次请求都是一次全新的相遇。所以登录认证的第二重本质,是 “将一次性的信任,转化为可持续的信任状态”。这就是我们熟知的状态延续机制:

  • Session-Cookie 机制:本质是有状态的中心化凭证。它就像去游乐园玩,门口检票后给你盖个章,每次玩项目给工作人员看一下手上的章,工作人员要去后台账本查这个章对不对。
  • Token (如 JWT) 机制:本质是自包含的去中心化契约。就如景区买的通票,上面用防伪印章写着你的有效期和权益。任何一个检票口只要用紫外线灯(公钥/密钥)验一下防伪,不需要查后台,就能放行。

这种延续,实际上是用时间成本换取操作便利。最终,登录认证是一场关于安全成本、用户体验与系统性能的三角博弈:

1
2
3
4
5
6
7
8
9
10
                    [安全安全 (Security)]
/\
/ \
/ \
/______\
[体验 (Usability)] [性能/成本 (Performance)]

# 极致的安全:每次操作都扫脸+动态验证码。结果:用户直接卸载。
# 极致的体验:一次登录,终身免密。结果:设备一旦丢失,底裤都被看光。
# 极致的性能:完全不校验,或者用极其简单、不耗 CPU 的非加密 Token。结果:轻易被重放攻击伪造。

所以,一个好的认证系统,其本质是在不信任的网络链路上,用最低的摩擦力(体验损耗),最恰当的算力(性能),去构建一个足够抵御当前威胁的信任模型。


Cookie:

1
2
3
4
5
6
7
8
9
# Cookie:通过 HTTP 响应头和请求头来传递,当服务端想要在你的浏览器里记点东西时,它会在响应里写:
HTTP/1.1 200 OK
Set-Cookie: user_theme=dark; Path=/; Max-Age=3600; HttpOnly
Set-Cookie: JSESSIONID=xyz123; Path=/; Max-Age=3600; HttpOnly

# 浏览器收到后,会把 user_theme=dark 存本地。下次你访问这个网站的任何页面时,浏览器会自动在请求头带上:
GET /profile HTTP/1.1
Host: example.com
Cookie: user_theme=dark;JSESSIONID=xyz123

Session:当你首次访问一个需要记录状态的系统时:

  • 服务端在内存(或 Redis)中创建一个 Session 对象,并生成一个全局唯一的随机字符串,比如 JSESSIONID=xyz123(即 Session ID)。
  • 服务端通过上面的 Set-Cookie 机制,把这个 xyz123 塞进客户端的 Cookie 里。
  • 之后,只要你带着这个 Cookie 访问,服务端就会根据 xyz123 去内存里捞出属于你的 Session 容器,读写里面的数据。

理解了本质,我们就能回答很多架构设计上的核心问题:

  • 没有 Cookie,Session 还能用吗?答案是能。如果浏览器禁用了 Cookie,我们依然可以通过其他方式把 Session ID 传给服务器。比如把 Session ID 拼在 URL 后面(URL 重写,如 xxx/home?jsessionid=xyz123)),或者放在 HTTP 请求头 Header 的自定义字段里。
  • 没有 Session,Cookie 还能用吗?也可以。比如网站要记住你的语言偏好(中文还是英文),或者夜间模式切换。这种不需要涉及核心安全、不需要存在服务端的轻量数据,直接用 Cookie 存着就行,完全不需要 Session 参与。

Cookie 是一种物理媒介,是浏览器提供的一块 “小黑板”,谁都可以往上面写字,每次去服务器都得背着这块黑板。 Session 是一种业务逻辑,是服务器为了识别你而开辟的 “档案空间”。 它们的结合,不过是服务器把档案的索引号写在了浏览器的小黑板上而已。


Token JWT 机制

发现没有,上述 Session-Cookie 机制需要服务器存储 session(也就是用户的状态信息),这在分布式服务中往往存在 “Session 漂移/丢失” 等问题。为了解决这些问题,Session-Cookie 的方案就得为 session 开辟额外的公共存储空间(比如 redis 集群),这就演变成了集中式的 Session 管理,这也是现代中大型互联网应用最主流、最标准的做法。

另外,在前后端分离(或者 App、小程序)开发中,由于前端和后端往往部署在不同的域名下(比如前端 owlias.com,后端 api.owlias.com),处理跨域 Cookie 非常痛苦。于是,这个原因也促使 Token 认证成为了主流。

为了摆脱 “记忆” 的枷锁,衍生出了 JWT(JSON Web Token)。它不再依赖服务器的记忆,而是将 “身份、权益、过期时间” 打包,用密钥(Secret Key)进行数字签名,凝固成一份不可篡改的契约。它的思想就是彻底消灭服务端的 Session,既然存哪里都麻烦,那干脆不存了。服务器把用户数据、过期时间等信息用密钥签名后,直接加密成一段 Token 塞给客户端。客户端每次请求都带着,服务器只要用密钥验签,就能当场确认身份。

  • 优点:服务端完全不需要存储任何状态,连 Redis 都省了,水平扩展能力举世无双。
  • 缺点:最大的缺点就是难以主动撤销。JWT 释放了服务器的内存,却带来了 “覆水难收”的诅咒。一旦一份有效期 7 天的 Token 被签发出去,它就变成了一个在网络中不受控流窜的幽灵。除非服务器重新恢复“记忆”(去查黑名单),否则无法在中途收回这份契约,但这样就违背了无状态的初衷。